home *** CD-ROM | disk | FTP | other *** search
- #!/usr/bin/python
- # -*- coding: utf-8 -*
-
- '''
- Metadata anonymisation toolkit - GUI edition
- '''
-
- import gtk
- import gobject
-
- import gettext
- import locale
- import logging
- import os
- import sys
- import mimetypes
- import xml.sax
- import urllib2
-
- from lib import mat
- from lib import strippers
-
-
- logging.basicConfig(level=mat.LOGGING_LEVEL)
-
-
- class CFile(object):
- '''
- Contain the "parser" class of the file "filename"
- This class exist just to be "around" my parser.Generic_parser class,
- since the gtk.ListStore does not accept it.
- '''
- def __init__(self, filename, backup, add2archive):
- try:
- self.file = mat.create_class_file(filename, backup, add2archive)
- except:
- self.file = None
-
-
- class GUI:
- '''
- Main GUI class
- '''
- def __init__(self):
- # Preferences
- self.force = False
- self.backup = True
- self.add2archive = True
-
- # Main window
- self.window = gtk.Window()
- self.window.set_title('Metadata Anonymisation Toolkit')
- self.window.connect('destroy', gtk.main_quit)
- self.window.set_default_size(800, 600)
- self.logo = mat.get_sharedir('logo.png')
- icon = gtk.gdk.pixbuf_new_from_file_at_size(self.logo, 50, 50)
- self.window.set_icon(icon)
-
- self.accelerator = gtk.AccelGroup()
- self.window.add_accel_group(self.accelerator)
-
- vbox = gtk.VBox()
- self.window.add(vbox)
-
- menubar = self.__create_menu()
- toolbar = self.__create_toolbar()
- content = gtk.ScrolledWindow()
- content.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- vbox.pack_start(menubar, False, True, 0)
- vbox.pack_start(toolbar, False, True, 0)
- vbox.pack_start(content, True, True, 0)
-
- # parser.class - name - path - type - cleaned
- self.liststore = gtk.ListStore(object, str, str, str, str, str)
-
- self.treeview = gtk.TreeView(model=self.liststore)
- self.treeview.set_search_column(1) # filename column is searchable
- self.treeview.set_rules_hint(True) # alternate colors for rows
- self.treeview.set_rubber_banding(True) # mouse selection
- self.treeview.connect("key_press_event", self.treeview_keyboard_event)
- self.treeview.connect('row-activated', self.__popup_metadata)
- self.treeview.connect('drag_data_received',
- self.__on_drag_data_received)
- self.treeview.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
- gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP,
- [('text/uri-list', 0, 80), ], gtk.gdk.ACTION_COPY)
- self.__add_columns()
- self.selection = self.treeview.get_selection()
- self.selection.set_mode(gtk.SELECTION_MULTIPLE)
-
- content.add(self.treeview)
-
- self.statusbar = gtk.Statusbar()
- self.statusbar.push(1, _('Ready'))
- vbox.pack_start(self.statusbar, False, False, 0)
-
- self.window.show_all()
-
- def __create_toolbar(self):
- '''
- Returns a vbox object, which contains a toolbar with buttons
- '''
- toolbar = gtk.Toolbar()
-
- toolbutton = gtk.ToolButton(gtk.STOCK_ADD)
- toolbutton.set_label(_('Add'))
- toolbutton.connect('clicked', self.__add_files)
- toolbutton.set_tooltip_text(_('Add files'))
- toolbar.add(toolbutton)
-
- toolbutton = gtk.ToolButton(gtk.STOCK_CLEAR)
- toolbutton.set_label(_('Clean'))
- toolbutton.connect('clicked', self.__process_files, self.__mat_clean)
- toolbutton.set_tooltip_text(_('Clean selected files'))
- toolbar.add(toolbutton)
-
- toolbutton = gtk.ToolButton(gtk.STOCK_FIND)
- toolbutton.set_label(_('Check'))
- toolbutton.connect('clicked', self.__process_files, self.__mat_check)
- toolbutton.set_tooltip_text(_('Check selected files for harmful meta'))
- toolbar.add(toolbutton)
-
- toolbutton = gtk.ToolButton(stock_id=gtk.STOCK_QUIT)
- toolbutton.set_label(_('Quit'))
- toolbutton.connect('clicked', gtk.main_quit)
- toolbar.add(toolbutton)
-
- vbox = gtk.VBox(spacing=3)
- vbox.pack_start(toolbar, False, False, 0)
- return vbox
-
- def __add_columns(self):
- '''
- Create the columns, and add them to the treeview
- '''
- colname = [_('Path'), _('Filename'), _('Mimetype'), _('State'),
- _('Cleaned file')]
-
- for i, j in enumerate(colname, 1):
- filename_column = gtk.CellRendererText()
- column = gtk.TreeViewColumn(j, filename_column, text=i)
- column.set_sort_column_id(i)
- column.set_resizable(True) # column is resizeable
- self.treeview.append_column(column)
-
- def __create_menu_item(self, name, func, menu, pix, shortcut):
- '''
- Create a MenuItem() like Preferences, Quit, Add, Clean, ...
- '''
- item = gtk.ImageMenuItem()
- if shortcut:
- key, mod = gtk.accelerator_parse(shortcut)
- item.add_accelerator('activate', self.accelerator,
- key, mod, gtk.ACCEL_VISIBLE)
- picture = gtk.Image()
- picture.set_from_stock(pix, gtk.ICON_SIZE_MENU)
- item.set_image(picture)
- item.set_label('_' + name)
- item.set_use_underline(True)
- item.connect('activate', func)
- menu.append(item)
-
- def __create_sub_menu(self, name, menubar):
- '''
- Create a submenu like File, Edit, Clean, ...
- '''
- submenu = gtk.Menu()
- menuitem = gtk.MenuItem()
- menuitem.set_submenu(submenu)
- menuitem.set_label('_' + name)
- menuitem.set_use_underline(True)
- menubar.append(menuitem)
- return submenu
-
- def __create_menu(self):
- '''
- Return a MenuBar
- '''
- menubar = gtk.MenuBar()
-
- file_menu = self.__create_sub_menu(_('Files'), menubar)
- self.__create_menu_item(_('Add files'), self.__add_files, file_menu,
- gtk.STOCK_ADD, '<Control>O')
- self.__create_menu_item(_('Quit'), gtk.main_quit, file_menu,
- gtk.STOCK_QUIT, '<Control>Q')
-
- edit_menu = self.__create_sub_menu(_('Edit'), menubar)
- self.__create_menu_item(_('Clear the filelist'),
- lambda x: self.liststore.clear(), edit_menu, gtk.STOCK_REMOVE,
- None)
- self.__create_menu_item(_('Preferences'), self.__preferences,
- edit_menu, gtk.STOCK_PREFERENCES, '<Control>P')
-
- process_menu = self.__create_sub_menu(_('Process'), menubar)
- item = gtk.ImageMenuItem()
- key, mod = gtk.accelerator_parse('<Control>L')
- item.add_accelerator('activate', self.accelerator,
- key, mod, gtk.ACCEL_VISIBLE)
- picture = gtk.Image()
- picture.set_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_MENU)
- item.set_image(picture)
- item.set_label(_('Clean'))
- item.connect('activate', self.__process_files, self.__mat_clean)
- process_menu.append(item)
-
- item = gtk.ImageMenuItem()
- key, mod = gtk.accelerator_parse('<Control>h')
- item.add_accelerator('activate', self.accelerator,
- key, mod, gtk.ACCEL_VISIBLE)
- picture = gtk.Image()
- picture.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
- item.set_image(picture)
- item.set_label(_('Check'))
- item.connect('activate', self.__process_files, self.__mat_check)
- process_menu.append(item)
-
- help_menu = self.__create_sub_menu(_('Help'), menubar)
- self.__create_menu_item(_('Supported formats'), self.__supported,
- help_menu, gtk.STOCK_INFO, False)
- self.__create_menu_item(_('About'), self.__about, help_menu,
- gtk.STOCK_ABOUT, False)
-
- return menubar
-
- def treeview_keyboard_event(self, widget, event):
- '''
- Remove selected files from the treeview
- when the use hit the 'suppr' key
- '''
- if gtk.gdk.keyval_name(event.keyval) == "Delete":
- rows = []
- self.selection.selected_foreach(
- lambda model, path, iter: rows.append(iter))
- [self.liststore.remove(i) for i in rows]
-
- def __add_files(self, button):
- '''
- Add the files chosed by the filechoser ("Add" button)
- '''
- chooser = gtk.FileChooserDialog(title=_('Choose files'),
- parent=self.window, action=gtk.FILE_CHOOSER_ACTION_OPEN,
- buttons=(gtk.STOCK_OK, 0, gtk.STOCK_CANCEL, 1))
- chooser.set_default_response(0)
- chooser.set_select_multiple(True)
-
- all_filter = gtk.FileFilter() # filter that shows all files
- all_filter.set_name(_('All files'))
- all_filter.add_pattern('*')
- chooser.add_filter(all_filter)
-
- supported_filter = gtk.FileFilter()
- # filter that shows only supported formats
- [supported_filter.add_mime_type(i) for i in strippers.STRIPPERS.keys()]
- supported_filter.set_name(_('Supported files'))
- chooser.add_filter(supported_filter)
-
- response = chooser.run()
-
- if response is 0: # gtk.STOCK_OK
- filenames = chooser.get_filenames()
- task = self.populate(filenames)
- gobject.idle_add(task.next) # asynchrone processing
- chooser.destroy()
-
- def populate(self, filenames):
- '''
- Append selected files by add_file to the self.liststore
- '''
- not_supported = []
- for filename in filenames: # filenames : all selected files/folders
- if os.path.isdir(filename): # if "filename" is a directory
- for root, dirs, files in os.walk(filename):
- for item in files:
- path_to_file = os.path.join(root, item)
- if self.__add_file_to_treeview(path_to_file):
- not_supported.append(item)
- else: # filename is a regular file
- if self.__add_file_to_treeview(filename):
- not_supported.append(filename)
- yield True
- if not_supported:
- self.__popup_non_supported(not_supported)
- yield False
-
- def __add_file_to_treeview(self, filename):
- '''
- Add a file to the list if it's format is supported
- '''
- if not os.path.isfile(filename):
- # if filename does not exist
- return False
-
- cf = CFile(filename, self.backup, self.add2archive)
- if cf.file is not None: # if the file is supported by the mat
- self.liststore.append([cf, os.path.dirname(cf.file.filename) + os.path.sep,
- cf.file.basename, cf.file.mime, _('unknow'), 'None'])
- return False
- else:
- return True
-
- def __popup_metadata(self, widget, row, col):
- '''
- Popup that display on double-clic
- metadata from a file
- '''
- label = '<b>%s</b>\'s metadatas:\n' % self.liststore[row][1]
- meta = ''
- if self.liststore[row][4] == _('Clean') or\
- self.liststore[row][0].file.is_clean():
- meta = 'No metadata found'
- self.liststore[row][4] = _('Clean')
- else:
- self.liststore[row][4] = _('Dirty')
- iterator = self.liststore[row][0].file.get_meta().iteritems()
- for i, j in iterator:
- name = '-<b>' + str(i) + '</b> : '
- meta += (name + str(j) + '\n')
-
- w = gtk.MessageDialog(self.window,
- gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, label)
- w.set_resizable(True)
- w.set_size_request(400, 300)
- scrolled_window = gtk.ScrolledWindow()
- scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- w.vbox.pack_start(scrolled_window, True, True, 0)
- content = gtk.Label(meta)
- content.set_selectable(True)
- content.set_alignment(0, 0)
- content.set_use_markup(True)
- scrolled_window.add_with_viewport(content)
- w.set_markup(label)
- w.show_all()
- click = w.run()
- if click:
- w.destroy()
-
- def __popup_non_supported(self, filelist):
- '''
- Popup that warn the user about the unsupported files
- that he want to process
- '''
- dialog = gtk.Dialog(title=_('Not-supported'), parent=self.window,
- flags=0, buttons=(gtk.STOCK_OK, 0))
- vbox = gtk.VBox(spacing=5)
- dialog.get_content_area().pack_start(vbox, True, True, 0)
- store = gtk.ListStore(str, str)
-
- # append filename - mimetype to the store
- #FIXME : I'm ugly
- for item in filelist:
- mime = mimetypes.guess_type(item)[0]
- if mime:
- store.append([item, mime])
- else:
- store.append([item, 'unknown'])
-
- treeview = gtk.TreeView(store)
- vbox.pack_start(treeview, True, True, 0)
-
- #create column
- rendererText = gtk.CellRendererText()
- column = gtk.TreeViewColumn(_('Filename'), rendererText, text=0)
- treeview.append_column(column)
- column = gtk.TreeViewColumn(_('Mimetype'), rendererText, text=1)
- treeview.append_column(column)
-
- dialog.show_all()
- click = dialog.run()
- if click is 0: # Ok button
- dialog.destroy()
-
- def __about(self, button):
- '''
- About popup
- '''
- w = gtk.AboutDialog()
- w.set_authors(['Julien (jvoisin) Voisin', ])
- w.set_artists(['Marine Benoît', ])
- w.set_copyright('GNU Public License v2')
- w.set_comments(_('This software was coded during the GSoC 2011'))
- w.set_logo(gtk.gdk.pixbuf_new_from_file_at_size(self.logo, 400, 200))
- w.set_program_name('Metadata Anonymisation Toolkit')
- w.set_version(mat.__version__)
- w.set_website('https://mat.boum.org')
- w.set_website_label(_('Website'))
- w.set_position(gtk.WIN_POS_CENTER)
- w.run()
- w.destroy()
-
- def __supported(self, button):
- '''
- List the supported formats
- '''
- dialog = gtk.Dialog(_('Supported formats'), self.window, 0,
- (gtk.STOCK_CLOSE, 0))
- vbox = gtk.VBox(spacing=5)
- dialog.get_content_area().pack_start(vbox, True, True, 0)
-
- label = gtk.Label()
- label.set_markup('<big><u>Supported fileformats</u></big>')
- vbox.pack_start(label, True, True, 0)
-
- #parsing xml
- handler = mat.XMLParser()
- parser = xml.sax.make_parser()
- parser.setContentHandler(handler)
- path = mat.get_sharedir('FORMATS')
- with open(path, 'r') as xmlfile:
- parser.parse(xmlfile)
-
- def expander_callback(current):
- ''' Close every expander except the current one '''
- for i in vbox.get_children()[1:]: # first child is a gtk.Label
- if i != current:
- i.set_expanded(False)
-
- for item in handler.list: # list of dict : one dict per format
- # create one expander per format
- # only if the format is supported
- if item['mimetype'].split(',')[0] in strippers.STRIPPERS:
- # some format have more than one mimetype
- title = '%s (%s)' % (item['name'], item['extension'])
- support = ('\t<b>%s</b> : %s' % ('support', item['support']))
- metadata = '\n\t<b>metadata</b> : ' + item['metadata']
- method = '\n\t<b>method</b> : ' + item['method']
- content = support + metadata + method
- if item['support'] == 'partial':
- content += '\n\t<b>remaining</b> : ' + item['remaining']
-
- expander = gtk.Expander(title)
- vbox.pack_start(expander, False, False, 0)
- label = gtk.Label()
- label.set_markup(content)
- expander.add(label)
- expander.connect('activate', expander_callback)
-
- dialog.show_all()
- click = dialog.run()
- if click is 0: # Close
- dialog.destroy()
-
- def __preferences(self, button):
- '''
- Preferences popup
- '''
- dialog = gtk.Dialog(_('Preferences'), self.window, 0,
- (gtk.STOCK_OK, 0))
- hbox = gtk.HBox()
- dialog.get_content_area().pack_start(hbox, False, False, 0)
-
- icon = gtk.Image()
- icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_DIALOG)
- hbox.pack_start(icon, False, False, 20)
-
- table = gtk.Table(3, 2, False) # nb rows, nb lines
- hbox.pack_start(table, True, True, 0)
-
- force = gtk.CheckButton(_('Force Clean'), False)
- force.set_active(self.force)
- force.connect('toggled', self.__invert, 'force')
- force.set_tooltip_text(_('Do not check if already clean before \
- cleaning'))
- table.attach(force, 0, 1, 0, 1)
-
- backup = gtk.CheckButton(_('Backup'), False)
- backup.set_active(self.backup)
- backup.connect('toggled', self.__invert, 'backup')
- backup.set_tooltip_text(_('Keep a backup copy'))
- table.attach(backup, 0, 1, 1, 2)
-
- add2archive = gtk.CheckButton(_('Add unsupported file to archives'),
- False)
- add2archive.set_active(self.add2archive)
- add2archive.connect('toggled', self.__invert, 'add2archive')
- add2archive.set_tooltip_text(_('Add non-supported (and so \
- non-anonymised) file to output archive'))
- table.attach(add2archive, 0, 1, 2, 3)
-
- hbox.show_all()
- response = dialog.run()
- if response is 0: # gtk.STOCK_OK
- dialog.destroy()
-
- def __invert(self, button, name):
- '''
- Invert a preference state
- '''
- if name == 'force':
- self.force = not self.force
- elif name == 'backup':
- self.backup = not self.backup
- for line in xrange(len(self.liststore)):
- # change the "backup" property of all files
- self.liststore[line][0].file.backup = self.backup
- self.treeview.get_column(4).set_visible(self.backup)
- elif name == 'add2archive':
- self.add2archive = not self.add2archive
-
- def __on_drag_data_received(self, widget, context,
- x, y, selection, target_type, timestamp):
- '''
- This function is called when something is
- drag'n'droped into mat.
- It basically add files.
- '''
- urls = selection.data.strip('\r\n\x00') # strip stupid characters
- cleaned_urls = map(self.__clean_draged_file_path, urls.split('\n'))
- task = self.populate(cleaned_urls)
- gobject.idle_add(task.next) # asynchrone processing
-
- def __clean_draged_file_path(self, url):
- '''
- Since the dragged urls are ugly,
- we need to process them
- '''
- url = urllib2.unquote(url) # unescape stupid chars
- if url.startswith('file:\\\\\\'): # windows
- return url[8:] # 8 is len('file:///')
- elif url.startswith('file://'): # nautilus, rox
- return url[7:] # 7 is len('file://')
- elif url.startswith('file:'): # xffm
- return url[5:] # 5 is len('file:')
-
- def __process_files(self, button, func):
- '''
- Launch the function "func" in a asynchrone way
- '''
- iterator = self.selection.get_selected_rows()[1]
- if not iterator: # if nothing is selected : select everything
- iterator = xrange(len(self.liststore))
- task = func(iterator) # launch func() in an asynchrone way
- gobject.idle_add(task.next)
-
- def __mat_check(self, iterator):
- '''
- Check if selected elements are clean
- '''
- for line in iterator: # for each file in selection
- self.statusbar.push(0, _('Checking %s...') % self.liststore[line][1])
- if self.force is True or self.liststore[line][4] != _('Clean'):
- if self.liststore[line][0].file.is_clean():
- string = _('Clean')
- else:
- string = _('Dirty')
- logging.info('%s is %s' % (self.liststore[line][1], string))
- self.liststore[line][4] = string
- yield True
- self.statusbar.push(0, _('Ready'))
- yield False
-
- def __mat_clean(self, iterator):
- '''
- Clean selected elements
- '''
- for line in iterator: # for each file in selection
- logging.info('Cleaning %s' % self.liststore[line][1])
- self.statusbar.push(0, _('Cleaning %s...') % self.liststore[line][1])
- if self.force is True or self.liststore[line][4] != _('Clean'):
- if self.liststore[line][0].file.remove_all():
- self.liststore[line][4] = _('Clean')
- if self.backup: # the backup copy state
- self.liststore[line][5] = os.path.basename(self.liststore[line][0].file.output)
- yield True
- self.statusbar.push(0, _('Ready'))
- yield False
-
- if __name__ == '__main__':
- gettext.install('MAT', unicode=True)
-
- #Main
- gui = GUI()
-
- #Add files from command line
- infiles = [arg for arg in sys.argv[1:] if os.path.exists(arg)]
- if infiles:
- task = gui.populate(infiles)
- gobject.idle_add(task.next)
-
- gtk.main()
-